/*------------------------------------------------------------------------------
* config.cpp : configuration module
*
* Copyright (C) 2020-2099 by Ubiquitous Navigation & Integrated positioning lab in Quest (UNIQ), all rights reserved.
*    This file is part of GAMP II - GOOD (Gnss Observations and prOducts Downloader)
*
* history : 2020/08/16 1.0  new (by Feng Zhou)
*           2020/10/12 3.0  mojor modifications (by Feng Zhou)
*-----------------------------------------------------------------------------*/
#include "../common/common.h"
#include "../common/types.h"
#include "../common/cstring.h"
#include "../common/gtime.h"
#include "../common/logger.h"
#include "ftps.h"
#include "config.h"

#ifdef _WIN32  /* for Windows */
#include "yaml-cpp\yaml.h"
#else          /* for Linux or Mac */
#include <yaml-cpp/yaml.h>
#endif

/* constants/macros ----------------------------------------------------------*/
#define MIN(x,y)    ((x) <= (y) ? (x) : (y))

/* function definition -------------------------------------------------------*/

/**
* @brief   : init - some initializations before processing
* @param[O]: popt (processing options)
* @param[O]: fopt (FTP options, nullptr:NO output)
* @return  : none
* @note    :
**/
void Config::init(prcopt_t* popt, ftpopt_t* fopt)
{
    /* time settings */
    popt->ts = { 0 };                            /* start time for processing */
    popt->ndays = 1;                             /* number of consecutive days */

    /* FTP downloading settings */
    fopt->minus_add_1day = true;                 /* (0:off  1:on) the day before and after the current day for precise satellite orbit and clock
                                                    products downloading */
    fopt->merge_sp3files = false;                /* (0: off  1: on) to merge three consecutive sp3 files into one file */
    fopt->printinfo4wget = false;                /* (0:off  1:on) print the information generated by 'wget' */

    /* initialization for FTP options */
    fopt->ftpdownloading = false;                /* the master switch for data downloading, 0:off  1:on, only for data downloading */
    fopt->getobs = false;                        /* (0:off  1:on) IGS observation (RINEX version 2.xx, short name 'd') */
    fopt->l2s4obs = 1;                           /* valid only for the observation files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getnav = false;                        /* (0:off  1:on) broadcast ephemeris */
    fopt->l2s4nav = 1;                           /* valid only for the navigation files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getorbclk = false;                     /* (0:off  1:on) precise orbit 'sp3' and precise clock 'clk' */
    fopt->l2s4oc = 1;                            /* valid only for the precise orbit and clock files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->geteop = false;                        /* (0:off  1:on) earth rotation parameter */
    fopt->l2s4eop = 1;                           /* valid only for EOP file with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getobx = false;                        /* (0:off  1:on) ORBEX (ORBit EXchange format) for satellite attitude information */
    fopt->getsnx = false;                        /* (0:off  1:on) IGS weekly SINEX */
    fopt->l2s4snx = 1;                           /* valid only for IGS weekly SINEX files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getdsb = false;                        /* (0:off  1:on) differential code/signal bias (DCB/DSB) */
    fopt->getosb = false;                        /* (0:off  1:on) observable-specific signal bias (OSB) */
    fopt->getion = false;                        /* (0:off  1:on) global ionosphere map (GIM) */
    fopt->l2s4ion = 1;                           /* valid only for the ionosphere files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getroti = false;                       /* (0:off  1:on) rate of TEC index (ROTI) */
    fopt->gettrp = false;                        /* (0:off  1:on) CODE and/or IGS tropospheric product */
    fopt->l2s4trp = 1;                           /* valid only for the troposphere files with long name, 0: long name, 1: short name, 2: long and short name */
    fopt->getatx = false;                        /* (0:off  1:on) ANTEX format antenna phase center correction */
} /* end of init */

/**
* @brief   : ReadCfgYaml - read configure file with with YAML format to get processing options
* @param[I]: cfgfile (configure file)
* @param[O]: popt (processing options)
* @param[O]: fopt (FTP options, nullptr:NO output)
* @return  : true:ok, false:error
* @note    :
**/
bool Config::ReadCfgYaml(std::string cfgfile, prcopt_t* popt, ftpopt_t* fopt)
{
    YAML::Node config;
    try
    {
        config = YAML::LoadFile(cfgfile);
    }
    catch (YAML::Exception& exception)
    {
        Logger::Trace(TERROR, "*** ERROR(Config::ReadCfgYaml): Failed to read the configuration file. Please check the path and format of the configuration file!");

        return false;
    }

    std::vector<std::string> msg;
    std::string sep;
    sep.push_back((char)FILEPATHSEP);
    /* the root/main directory of GNSS observations and products */
    try
    {
        popt->maindir = config["mainDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("mainDir");
    }

    /* the sub-directory of RINEX format observation files */
    try
    {
        popt->obsdir = popt->maindir + sep + config["obsDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("obsDir");
    }

    /* the sub-directory of RINEX format broadcast ephemeris files */
    try
    {
        popt->navdir = popt->maindir + sep + config["navDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("navDir");
    }

    /* the sub-directory of SP3 format precise ephemeris files */
    try
    {
        popt->orbdir = popt->maindir + sep + config["orbDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("orbDir");
    }

    /* the sub-directory of RINEX format precise clock files */
    try
    {
        popt->clkdir = popt->maindir + sep + config["clkDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("clkDir");
    }

    /* the sub-directory of earth rotation/orientation parameter (EOP) files */
    try
    {
        popt->eopdir = popt->maindir + sep + config["eopDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("eopDir");
    }

    /* the sub-directory of MGEX final/rapid and/or CNES real-time ORBEX (ORBit EXchange format) files */
    try
    {
        popt->obxdir = popt->maindir + sep + config["obxDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("obxDir");
    }

    /* the sub-directory of CODE and/or MGEX differential code bias (DCB) files */
    try
    {
        popt->biadir = popt->maindir + sep + config["biaDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("biaDir");
    }

    /* the sub-directory of SINEX format IGS weekly solution files */
    try
    {
        popt->snxdir = popt->maindir + sep + config["snxDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("snxDir");
    }

    /* the sub-directory of CODE and/or IGS global ionosphere map (GIM) files */
    try
    {
        popt->iondir = popt->maindir + sep + config["ionDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("ionDir");
    }

    /* the sub-directory of CODE and/or IGS tropospheric product files */
    try
    {
        popt->ztddir = popt->maindir + sep + config["ztdDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("ztdDir");
    }

    /* the sub-directory of table files for processing */
    try
    {
        popt->tbldir = popt->maindir + sep + config["tblDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("tblDir");
    }

    /* the sub-directory of log file */
    try
    {
        popt->logdir = popt->maindir + sep + config["logDir"].as<std::string>();
        popt->logfil = popt->logdir + sep + "log.txt";
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("logDir");
    }

    /* the directory where third-party softwares (i.e., 'wget', 'gzip', 'crx2rnx' etc) are stored */
    try
    {
        popt->dir3party = popt->maindir + sep + config["3partyDir"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("3partyDir");
    }

    /* time settings */
    try
    {
        std::string sline = config["procTime"].as<std::string>();
        std::vector<std::string> stemp;
        std::vector<int> itemp;
        stemp = CString::split(sline, " ");
        for (size_t i = 0; i < stemp.size(); i++) itemp.push_back(stoi(stemp[i]));
        int j = itemp[0];
        if (j == 1)  /* year, month, day */
        {
            if (itemp.size() != 5)
            {
                Logger::Trace(TERROR, "*** ERROR(PreProcess::ReadCfgYaml): the setting of 'year, month, day' is wrong, please check it!");
                return false;
            }
            std::vector<double> ep;
            ep.push_back(itemp[1]); ep.push_back(itemp[2]); ep.push_back(itemp[3]);
            ep.push_back(0); ep.push_back(0); ep.push_back(0);
            popt->ts = GTime::ymdhms2time(ep);
            popt->ndays = itemp[4];
        }
        else if (j == 2)  /* year, day of year */
        {
            if (itemp.size() != 4)
            {
                Logger::Trace(TERROR, "*** ERROR(PreProcess::ReadCfgFile): the setting of 'year, day of year' is wrong, please check it!");
                return false;
            };
            popt->ts = GTime::yrdoy2time(itemp[1], itemp[2]);
            popt->ndays = itemp[3];
        }
        else if (j == 3)  /* GPS week, day within week */
        {
            if (itemp.size() != 4)
            {
                Logger::Trace(TERROR, "*** ERROR(PreProcess::ReadCfgFile): the setting of 'GPS week, day within week' is wrong, please check it!");
                return false;
            };
            double sow = itemp[2] * 86400;
            popt->ts = GTime::gpst2time(itemp[1], sow);
            popt->ndays = itemp[3];
        }
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("procTime");
    }

    /* FTP downloading settings */
    try
    {
        /* (0:off  1:on) the day before and after the current day for precise satellite orbit and clock products downloading */
        fopt->minus_add_1day = config["minusAdd1day"].as<int>() == 1 ? true : false;
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("minusAdd1day");
    }

    try
    {
        /* (0: off  1: on) to merge three consecutive sp3 files into one file */
        fopt->merge_sp3files = config["merge_sp3files"].as<int>() == 1 ? true : false;
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("merge_sp3files");
    }

    try
    {
        /* (0:off  1:on) print the information generated by 'wget' */
        fopt->printinfo4wget = config["printInfoWget"].as<int>() == 1 ? true : false;
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("printInfoWget");
    }

    /* handling of FTP downloading */
    try
    {
        fopt->ftpdownloading = config["ftpDownloading"]["opt4ftp"].as<int>() == 1 ? true : false;
        fopt->ftpfrom = config["ftpDownloading"]["ftpArch"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("ftpDownloading");
    }

    int hh = 0, nh = 0, imax = 0, step = 1;
    try
    {
        fopt->getobs = config["getObs"]["opt4obs"].as<int>() == 1 ? true : false;               /* (0:off  1:on) GNSS observation data */
        fopt->obstype = config["getObs"]["obsType"].as<std::string>();
        fopt->obsfrom = config["getObs"]["obsFrom"].as<std::string>();
        fopt->obslist = popt->maindir + sep + config["getObs"]["obsList"].as<std::string>();
        int hh = config["getObs"]["sHH4obs"].as<int>();
        int nh = config["getObs"]["nHH4obs"].as<int>();
        int imax = MIN(hh + nh, 24);
        for (int i = hh; i < imax; i++) fopt->hhobs.push_back(i);
        fopt->l2s4obs = config["getObs"]["l2s4obs"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getObs");
    }

    try
    {
        fopt->getnav = config["getNav"]["opt4nav"].as<int>() == 1 ? true : false;               /* (0:off  1:on) broadcast ephemeris */
        fopt->navtype = config["getNav"]["navType"].as<std::string>();
        fopt->navsys = config["getNav"]["navSys"].as<std::string>();
        fopt->navfrom = config["getNav"]["navFrom"].as<std::string>();
        fopt->navlist = popt->maindir + sep + config["getNav"]["navList"].as<std::string>();
        hh = config["getNav"]["sHH4nav"].as<int>();
        nh = config["getNav"]["nHH4nav"].as<int>();
        imax = MIN(hh + nh, 24);
        for (int i = hh; i < imax; i++) fopt->hhnav.push_back(i);
        fopt->l2s4nav = config["getNav"]["l2s4nav"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getNav");
    }

    try
    {
        fopt->getorbclk = config["getOrbClk"]["opt4oc"].as<int>() == 1 ? true : false;         /* (0:off  1:on) precise orbit and clock */
        fopt->orbclkfrom = config["getOrbClk"]["ocFrom"].as<std::string>();
        hh = config["getOrbClk"]["sHH4oc"].as<int>();
        nh = config["getOrbClk"]["nHH4oc"].as<int>();

        std::string ocopt = fopt->orbclkfrom;
        std::vector<std::string> acs;
        int ipos = (int)ocopt.find_first_of('+');
        if (ipos > 0) acs = CString::split(ocopt, "+");
        else acs.push_back(ocopt);
        fopt->hhorbclk.resize(4);  /* for "esa_u", "gfz_u", "igs_u", and "whu_u" */
        for (size_t i = 0; i < acs.size(); i++)
        {
            std::string ac_i = acs[i];
            if (ac_i == "igs_u" || ac_i == "esa_u") step = 6;
            else if (ac_i == "gfz_u") step = 3;
            else if (ac_i == "whu_u") step = 1;
            else step = 24;
            int imax = 24;
            for (int i = 0; i < imax; i += step)
            {
                if (hh > i) hh = i + step;
                else break;
            }
            imax = MIN(hh + nh * step, 24);
            for (int i = hh; i < imax; i += step)
            {
                if (ac_i == "esa_u") fopt->hhorbclk[0].push_back(i);
                else if (ac_i == "gfz_u") fopt->hhorbclk[1].push_back(i);
                else if (ac_i == "igs_u") fopt->hhorbclk[2].push_back(i);
                else if (ac_i == "whu_u") fopt->hhorbclk[3].push_back(i);
            }
        }

        fopt->l2s4oc = config["getOrbClk"]["l2s4oc"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getOrbClk");
    }

    try
    {
        fopt->geteop = config["getEop"]["opt4eop"].as<int>() == 1 ? true : false;               /* (0:off  1:on) earth rotation parameter */
        fopt->eopfrom = config["getEop"]["eopFrom"].as<std::string>();
        hh = config["getEop"]["sHH4eop"].as<int>();
        nh = config["getEop"]["nHH4eop"].as<int>();

        std::string eopt = fopt->eopfrom;
        if (eopt == "igs_u" || eopt == "esa_u") step = 6;
        else if (eopt == "gfz_u") step = 3;
        else step = 24;
        imax = 24;
        for (int i = 0; i < imax; i += step)
        {
            if (hh > i) hh = i + step;
            else break;
        }
        imax = MIN(hh + nh * step, 24);
        for (int i = hh; i < imax; i += step) fopt->hheop.push_back(i);

        fopt->l2s4eop = config["getEop"]["l2s4eop"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getEop");
    }

    try
    {
        fopt->getobx = config["getObx"]["opt4obx"].as<int>() == 1 ? true : false;               /* (0:off  1:on) ORBEX (ORBit EXchange format) for satllite attitude information */
        fopt->obxfrom = config["getObx"]["obxFrom"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getObx");
    }

    try
    {
        fopt->getdsb = config["getDsb"]["opt4dsb"].as<int>() == 1 ? true : false;               /* (0:off  1:on) differential code/signal bias (DCB/DSB) */
        fopt->dsbfrom = config["getDsb"]["dsbFrom"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getDsb");
    }

    try
    {
        fopt->getosb = config["getOsb"]["opt4osb"].as<int>() == 1 ? true : false;               /* (0:off  1:on) observable-specific signal bias (OSB) */
        fopt->osbfrom = config["getOsb"]["osbFrom"].as<std::string>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getOsb");
    }

    try
    {
        fopt->getsnx = config["getSnx"]["opt4snx"].as<int>() == 1 ? true : false;               /* (0:off  1:on) IGS weekly SINEX */
        fopt->l2s4snx = config["getSnx"]["l2s4snx"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getSnx");
    }

    try
    {
        fopt->getion = config["getIon"]["opt4ion"].as<int>() == 1 ? true : false;               /* (0:off  1:on) CODE and/or IGS global ionosphere map (GIM) */
        fopt->ionfrom = config["getIon"]["ionFrom"].as<std::string>();
        fopt->l2s4ion = config["getIon"]["l2s4ion"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getIon");
    }

    try
    {
        fopt->getroti = config["getRoti"]["opt4rot"].as<int>() == 1 ? true : false;       /* (0:off  1:on) Rate of TEC index (ROTI) */
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getRoti");
    }

    try
    {
        fopt->gettrp = config["getTrp"]["opt4trp"].as<int>() == 1 ? true : false;               /* (0:off  1:on) CODE and/or IGS tropospheric product */
        fopt->trpfrom = config["getTrp"]["trpFrom"].as<std::string>();
        fopt->trplist = popt->maindir + sep + config["getTrp"]["trpList"].as<std::string>();
        fopt->l2s4trp = config["getTrp"]["l2s4trp"].as<int>();
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getTrp");
    }

    try
    {
        fopt->getatx = config["getAtx"]["opt4atx"].as<int>() == 1 ? true : false;          /* (0:off  1:on) ANTEX format antenna phase center correction */
    }
    catch (YAML::Exception& exception)
    {
        msg.push_back("getAtx");
    }

    if ((int)msg.size() > 0)
    {
        Logger::Trace(TERROR, "*** ERROR(PreProcess::ReadCfgYaml): Failed when loading the following settings: ");
        for (size_t i = 0; i < msg.size(); i++) Logger::Trace(TERROR, "  " + std::to_string(i + 1) + ". " + msg[i]);
        Logger::Trace(TERROR, "* Please check the corresponding settings in the configuration file: " + cfgfile);

        return false;
    }

    return true;
} /* end of ReadCfgYaml */

/**
* @brief   : run - start GOOD processing
* @param[I]: cfgFile (configure file with full path)
* @param[O]: none
* @return  : none
* @note    :
**/
void Config::run(std::string cfgfile)
{
    prcopt_t popt;
    ftpopt_t fopt;
    /* initialization */
    init(&popt, &fopt);

    /* read configure file to get processing information */
    bool stat = false;
    stat = ReadCfgYaml(cfgfile, &popt, &fopt);
    if (!stat) return;

    /* data downloading for GNSS further processing */
    if (fopt.ftpdownloading)
    {
        FtpUtil ftp;
        std::string obsdirmain = popt.obsdir;
        std::string navdirmain = popt.navdir;
        std::string iondirmain = popt.iondir;
        std::string ztddirmain = popt.ztddir;
        for (int i = 0; i < popt.ndays; i++)
        {
            int yyyy, doy;
            GTime::time2yrdoy(popt.ts, yyyy, doy);
            std::string syyyy = CString::int2str(yyyy, 4);
            std::string sdoy = CString::int2str(doy, 3);
            std::string dir, sep;
            sep.push_back((char)FILEPATHSEP);
            /* creat new observation sub-directory */
            if (fopt.getobs)
            {
                dir = obsdirmain + sep + syyyy + sep + sdoy;
                CString::trim(dir);
                popt.obsdir = dir;
                if (access(dir.c_str(), 0) == -1)
                {
                    /* If the directory does not exist, creat it */
#ifdef _WIN32   /* for Windows */
                    std::string cmd = "mkdir " + dir;
#else           /* for Linux or Mac */
                    std::string cmd = "mkdir -p " + dir;
#endif
                    std::system(cmd.c_str());
                }
            }

            /* creat new NAV sub-directory */
            if (fopt.getnav)
            {
                dir = navdirmain + sep + syyyy + sep + sdoy;
                CString::trim(dir);
                popt.navdir = dir;
                if (access(dir.c_str(), 0) == -1)
                {
                    /* If the directory does not exist, creat it */
#ifdef _WIN32   /* for Windows */
                    std::string cmd = "mkdir " + dir;
#else           /* for Linux or Mac */
                    std::string cmd = "mkdir -p " + dir;
#endif
                    std::system(cmd.c_str());
                }
            }

            /* creat new ION sub-directory */
            if (fopt.getion)
            {
                dir = iondirmain + sep + syyyy + sep + sdoy;
                CString::trim(dir);
                popt.iondir = dir;
                if (access(dir.c_str(), 0) == -1)
                {
                    /* If the directory does not exist, creat it */
#ifdef _WIN32   /* for Windows */
                    std::string cmd = "mkdir " + dir;
#else           /* for Linux or Mac */
                    std::string cmd = "mkdir -p " + dir;
#endif
                    std::system(cmd.c_str());
                }
            }

            /* creat new ZTD sub-directory */
            if (fopt.gettrp)
            {
                dir = ztddirmain + sep + syyyy + sep + sdoy;
                CString::trim(dir);
                popt.ztddir = dir;
                if (access(dir.c_str(), 0) == -1)
                {
                    /* If the directory does not exist, creat it */
#ifdef _WIN32   /* for Windows */
                    std::string cmd = "mkdir " + dir;
#else           /* for Linux or Mac */
                    std::string cmd = "mkdir -p " + dir;
#endif
                    std::system(cmd.c_str());
                }
            }

            /* the main entry of FTP downloader */
            ftp.FtpDownload(&popt, &fopt);

            popt.ts = GTime::TimeAdd(popt.ts, 86400.0);
        }
    }
} /* end of run */